
/* usbnapi.c
* Copyright (C) 2006  Benedikt Sauter
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "usbnapi.h"

#if !O_USB_TINY

//////////////////////////////////////////////////////////////////////////////

extern void USBNDebugHex (uint8_t hex);

//////////////////////////////////////////////////////////////////////////////

typedef struct string_entry
{
	struct string_entry *next;
	
	// start of packet
	uint8_t bLength;
	uint8_t bDescriptorType;
	uint8_t data [];
} string_entry;

//////////////////////////////////////////////////////////////////////////////

struct _endp_t
{
	struct _endp_t *next;
	struct usb_endpoint_descriptor endp;
//	union
//	{
		txfunc_t *txfunc;
		rxfunc_t *rxfunc;
//	};
};

struct _interf_t
{
	struct _interf_t *next;
	struct _endp_t *first_endp;
	struct usb_interface_descriptor interf;
};

struct _conf_t
{
	struct _conf_t   *next;
	struct _interf_t *first_interf;
	struct usb_configuration_descriptor conf;
};

//////////////////////////////////////////////////////////////////////////////

static struct usb_device_descriptor DeviceDescriptor;
static conf_t *first_conf;
static struct string_entry *StringList;

//////////////////////////////////////////////////////////////////////////////

#if O_NEED_RAM_STRINGDESCRIPTORS

// string descriptor functions 
static uint8_t _USBNAddStringDescriptor (const char *string)
{
	uint8_t len = strlen (string);
	string_entry *tmp = (string_entry *) malloc (sizeof (string_entry) + len * 2);
	if (tmp == NULL)
		return 0;

	tmp->next  = NULL;
	tmp->bLength = 2 + len * 2;
	tmp->bDescriptorType = STRING;

	// build unicode string, as defined in the usb spec
	uint8_t *p = tmp->data;
	while (len--)
	{
		*p++ = *string++;
		*p++ = 0x00;
	}


	//USBNDebug("add to string list\r\n");
	uint8_t index = 1;				// index starts at 1
	if (StringList == NULL)
	{
		//USBNDebug("first string elemennt\r\n");
		StringList = tmp;
	}
	else
	{
		//USBNDebug("next string element\r\n");
		index++;
		string_entry *p = StringList;
		while (p->next != NULL)
		{
			p = p->next;
			index++;
		}
		p->next = tmp;
	}
	
	return index;
}

#endif

#if !defined(MT_BOOTLOADER_ADDRESS) || (MT_BOOTLOADER_ADDRESS < 0xFFFF)

// the prog mem version only works when the strings are in the lower 64k ...

static uint8_t _USBNAddStringDescriptor_P (const prog_char *string)
{
	uint8_t len = strlen_P (string);
	string_entry *tmp = (string_entry *) malloc (sizeof (string_entry) + len * 2);
	if (tmp == NULL)
		return 0;

	tmp->next  = NULL;
	tmp->bLength = 2 + len * 2;
	tmp->bDescriptorType = STRING;

	// build unicode string, as defined in the usb spec
	uint8_t *p = tmp->data;
	while (len--)
	{
		*p++ = pgm_read_byte (string++);
		*p++ = 0x00;
	}


	//USBNDebug("add to string list\r\n");
	uint8_t index = 1;				// index starts at 1
	if (StringList == NULL)
	{
		//USBNDebug("first string elemennt\r\n");
		StringList = tmp;
	}
	else
	{
		//USBNDebug("next string element\r\n");
		index++;
		string_entry *p = StringList;
		while (p->next != NULL)
		{
			p = p->next;
			index++;
		}
		p->next = tmp;
	}
	
	return index;
}

#endif

//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
// setup global datastructure
void USBNInit (void)
{
	// one and only device descriptor
	DeviceDescriptor.bLength         = 0x12;	// length of device descriptor
	DeviceDescriptor.bDescriptorType = DEVICE;
	DeviceDescriptor.bcdUSB          = 0x0110;
	DeviceDescriptor.bDeviceClass    = 0x00;
	DeviceDescriptor.bDeviceSubClass = 0x00;
	DeviceDescriptor.bDeviceProtocol = 0x00;
	DeviceDescriptor.bMaxPacketSize0 = 0x08;
	DeviceDescriptor.idVendor        = 0x0000;
	DeviceDescriptor.idProduct       = 0x0000;
	DeviceDescriptor.bcdDevice       = 0x0000;
	DeviceDescriptor.iManufacturer   = 0x00;
	DeviceDescriptor.iProduct        = 0x00;
	DeviceDescriptor.iSerialNumber   = 0x00;
	DeviceDescriptor.bNumConfigurations = 0;

	// function caller for incomming data
	// [0] is unused, but for later extensions
	for (uint8_t i = 0; i < 4; i++)
	{
		rxfifos[i].epadr = 0;
		rxfifos[i].func = NULL;
		txfifos[i].epadr = 0;
		txfifos[i].func = NULL;
	}

	first_conf = NULL;
	StringList = NULL;
}

// set all device ids vendor/product/device
void USBNDeviceIDs (uint16_t idVendor, uint16_t idProduct, uint16_t bcdDevice)
{
	DeviceDescriptor.idVendor  = idVendor;
	DeviceDescriptor.idProduct = idProduct;
	DeviceDescriptor.bcdDevice = bcdDevice;
}

// set vendor id
void USBNDeviceVendorID (uint16_t idVendor)
{
	DeviceDescriptor.idVendor = idVendor;
}

//set product id
void USBNDeviceProductID (uint16_t idProduct)
{
	DeviceDescriptor.idProduct = idProduct;
}

//set device id
void USBNDeviceBCDDevice (uint16_t bcdDevice)
{
	DeviceDescriptor.bcdDevice = bcdDevice;
}


#if O_NEED_RAM_STRINGDESCRIPTORS

void USBNDeviceManufacture (const char *manufacture)
{
	DeviceDescriptor.iManufacturer = _USBNAddStringDescriptor (manufacture);
}

void USBNDeviceProduct (const char *product)
{
	DeviceDescriptor.iProduct = _USBNAddStringDescriptor (product);
}

void USBNDeviceSerialNumber (const char *serialnumber)
{
	DeviceDescriptor.iSerialNumber = _USBNAddStringDescriptor (serialnumber);
}

#endif


void USBNDeviceManufacture_P (const prog_char *manufacture)
{
	DeviceDescriptor.iManufacturer = _USBNAddStringDescriptor_P (manufacture);
}

void USBNDeviceProduct_P (const prog_char *product)
{
	DeviceDescriptor.iProduct = _USBNAddStringDescriptor_P (product);
}

void USBNDeviceSerialNumber_P (const prog_char *serialnumber)
{
	DeviceDescriptor.iSerialNumber = _USBNAddStringDescriptor_P (serialnumber);
}

// define class code
void USBNDeviceClass (uint8_t classcode)
{
	DeviceDescriptor.bDeviceClass = classcode;
}

// define sub class code
void USBNDeviceSubClass (uint8_t subclasscode)
{
	DeviceDescriptor.bDeviceSubClass = subclasscode;
}

// define protocol 
void USBNDeviceProtocol (uint8_t protocolcode)
{
	DeviceDescriptor.bDeviceProtocol = protocolcode;
}

//////////////////////////////////////////////////////////////////////////////
// CONFIGURATION ...

// add new configuration to device
conf_t *USBNAddConfiguration (void)
{
	uint8_t index = ++DeviceDescriptor.bNumConfigurations;

	conf_t *pc = (conf_t *) malloc (sizeof (conf_t));
	pc->next = NULL;
	pc->first_interf = NULL;

	pc->conf.bLength             = sizeof (struct usb_configuration_descriptor); // 9
	pc->conf.wTotalLength        = pc->conf.bLength;	// will be updated
	pc->conf.bDescriptorType     = CONFIGURATION;
	pc->conf.bNumInterfaces      = 0;			// will be increased
	pc->conf.bConfigurationValue = index;		// number of configuration
	pc->conf.iConfiguration      = 0x00;		// string index for configuration 
	pc->conf.bmAttributes        = 0xA0;		// bus powered
	pc->conf.MaxPower            = 0x1A;		// max power 
	
	// add to list of configurations
	conf_t *ptr = first_conf;
	if (ptr == NULL)
		first_conf = pc;
	else
	{
		while (ptr->next != NULL)
			ptr = ptr->next;
		ptr->next = pc;
	}
	return pc;
}

// give a name to the configuration
#if O_NEED_RAM_STRINGDESCRIPTORS
void USBNConfigurationName (conf_t *pc, const char *name)
{
	if (pc)
		pc->conf.iConfiguration = _USBNAddStringDescriptor (name);
}
#endif

void USBNConfigurationName_P (conf_t *pc, const prog_char *name)
{
	if (pc)
		pc->conf.iConfiguration = _USBNAddStringDescriptor_P (name);
}

void USBNConfigurationPower (conf_t *pc, uint16_t power)
{
	if (pc)
		pc->conf.MaxPower = power / 2;
}

//////////////////////////////////////////////////////////////////////////////
// INTERFACE ...

interf_t *USBNAddInterfaceClass (conf_t *pc, uint8_t number, char class, char subclass, char protocol)
{
//	uint8_t index =
	++pc->conf.bNumInterfaces;

	interf_t *pi = (interf_t *) malloc (sizeof (interf_t));
	pi->next = NULL;
	pi->first_endp = NULL;

	pi->interf.bLength            = sizeof (struct usb_interface_descriptor); // 9
	pi->interf.bDescriptorType    = INTERFACE;
	pi->interf.bInterfaceNumber   = number;				// FIXME
	pi->interf.bAlternateSetting  = 0;
	pi->interf.bNumEndpoints      = 0;
	pi->interf.bInterfaceClass    = class;
	pi->interf.bInterfaceSubClass = subclass;
	pi->interf.bInterfaceProtocol = protocol;
	pi->interf.iInterface         = 0x00;

	pc->conf.wTotalLength += pi->interf.bLength;
	
	// add to list of interfaces
	interf_t *ptr = pc->first_interf;
	if (ptr == NULL)
		pc->first_interf = pi;
	else
	{
		while (ptr->next != NULL)
			ptr = ptr->next;
		ptr->next = pi;
	}
	return pi;
}

interf_t *USBNAddInterface (conf_t *pc, uint8_t number)
{
	return USBNAddInterfaceClass (pc, number, 0, 0, 0);
}

void USBNAlternateSetting (conf_t *pc, interf_t *pi, uint8_t setting)
{
	(void) pc;
	if (pi)
		pi->interf.bAlternateSetting = setting;
}

//////////////////////////////////////////////////////////////////////////////
// ENDPOINT ...

static endp_t *_USBNAddEndpoint (conf_t *pc, interf_t *pi, uint8_t epnr, uint8_t epadr, char attr,
			uint8_t fifosize, uint8_t intervall, rxfunc_t *rxfunc, txfunc_t *txfunc)
{
	pi->interf.bNumEndpoints++;

	endp_t *pe = (endp_t *) malloc (sizeof (endp_t));
	pe->next = NULL;
	pe->rxfunc = rxfunc;
	pe->txfunc = txfunc;
	
	pe->endp.bLength          = sizeof (struct usb_endpoint_descriptor);
	pe->endp.bDescriptorType  = ENDPOINT;
	pe->endp.bEndpointAddress = epadr;
	pe->endp.bmAttributes     = attr;
	pe->endp.wMaxPacketSize   = (uint16_t) fifosize;
	pe->endp.bIntervall       = intervall;
	
	// adapt length of complete configuration packet
	pc->conf.wTotalLength += pe->endp.bLength;
	
	// add to list of interfaces
	endp_t *ptr = pi->first_endp;
	if (ptr == NULL)
		pi->first_endp = pe;
	else
	{
		while (ptr->next != NULL)
			ptr = ptr->next;
		ptr->next = pe;
	}
	return pe;
}

// add out/rx endpoint
endp_t *USBNAddOutEndpoint(conf_t *pc, interf_t *pi, uint8_t epnr, uint8_t epadr,
			char attr, uint8_t fifosize, uint8_t intervall, rxfunc_t *fkt)
{
	// FIXME (should be defined when set configuration is called)
	rxfifos[epnr].epadr = epadr;
	rxfifos[epnr].func = fkt;
	return _USBNAddEndpoint (pc, pi, epnr, epadr, attr, fifosize, intervall, fkt, NULL);
}

// add in/tx endpoint
endp_t *USBNAddInEndpoint(conf_t *pc, interf_t *pi, uint8_t epnr, uint8_t epadr,
			char attr, uint8_t fifosize, uint8_t intervall, txfunc_t *fkt)
{
	// FIXME (should be defined when set configuration is called)
	txfifos[epnr].epadr = epadr;
	txfifos[epnr].func = fkt;
	return _USBNAddEndpoint (pc, pi, epnr, epadr + 0x80, attr, fifosize, intervall, NULL, fkt);
}

//////////////////////////////////////////////////////////////////////////////

#define MEMCPYX(d, s, n)	{ memcpy (d, s, n);	d += n; }

//////////////////////////////////////////////////////////////////////////////

static uint8_t **FinalConfigurationArray;

// *******************************************************************
// put configuration/interface and ep descr in a linear field
// ********************************************************************
void USBNCreateConfDescrField (void)
{
	// count number of configurations
	uint8_t nconf = DeviceDescriptor.bNumConfigurations;

	// now get the array with the numbers of configurations 
	FinalConfigurationArray = (uint8_t **) malloc (sizeof(uint8_t *) * nconf);
	
	uint8_t config = 0;
	// get space and copy configuration to finalarray
	for (conf_t *pc = first_conf; pc; pc = pc->next)
	{
		// reserve memory for every configuration descriptor
		uint8_t *dst = (uint8_t *) malloc (sizeof(uint8_t) * pc->conf.wTotalLength);
		
		FinalConfigurationArray[config++] = dst;

		// copy configuration descriptor
		MEMCPYX (dst, &pc->conf, pc->conf.bLength);

		// copy interface and endpoint descr
		for (interf_t *pi = pc->first_interf; pi; pi = pi->next)
		{
			// copy interface
			MEMCPYX (dst, &pi->interf, pi->interf.bLength);
					
			// copy endpoints
			for (endp_t *pe = pi->first_endp; pe; pe = pe->next)
				MEMCPYX (dst, &pe->endp, pe->endp.bLength);
		}
	}

//	for (uint8_t i = 0; i < 32; i++)
//		USBNDebugHex (FinalConfigurationArray[0][i]);
}

//////////////////////////////////////////////////////////////////////////////

const uint8_t *UsbGetDeviceDescriptor (void)
{
	return (const uint8_t *) &DeviceDescriptor;
}

//////////////////////////////////////////////////////////////////////////////

const uint8_t *UsbGetConfigDescriptor (uint8_t index)
{
	return FinalConfigurationArray[index];
}

//////////////////////////////////////////////////////////////////////////////

static const uint8_t zero_packet [1] = { 0 };		// packet, where the data size is 0

const uint8_t *UsbGetString (uint8_t index)
{
	uint8_t  idx = 0;
	for (string_entry *tmp = StringList; tmp != NULL; tmp = tmp->next)
		if (++idx == index)
			return &tmp->bLength;
	return zero_packet;				// just in case
}

#endif // O_USB_TINY

